# Reflection

API отражения обеспечивает более сложное и полное представление о структуре кода. 
Оно обеспечивает представление типизированных абстрактных синтаксических деревьев и их свойств, 
таких как типы, символы, позиции и комментарии.

API можно использовать в макросах, а также для проверки [файлов TASTy](../metaprogramming/tasty-inspection.md).

### Как использовать API

API отражения определен в типе `Quotes` как `reflect`. 
Фактический экземпляр зависит от текущей области, 
в которой используются цитаты или сопоставление с образцом в цитатах. 
Следовательно, каждый метод макроса получает `Quotes` в качестве дополнительного аргумента. 
Поскольку `Quotes` является контекстным, для доступа к его членам нужно либо назвать параметр, либо вызвать его. 
Следующее определение из стандартной библиотеки подробно описывает канонический способ доступа к ней:

```dotty
package scala.quoted

transparent inline def quotes(using inline q: Quotes): q.type = q
```

Можно использовать `scala.quoted.quotes` для импорта текущей `Quotes` в область видимости:

```dotty
import scala.quoted.* // Import `quotes`, `Quotes`, and `Expr`

def f(x: Expr[Int])(using Quotes): Expr[Int] =
  import quotes.reflect.* // Import `Tree`, `TypeRepr`, `Symbol`, `Position`, .....
  val tree: Tree = ...
  ...
```

Это позволит импортировать все типы и модули (с методами расширения) API.


### Как ориентироваться в API

Полный API можно найти в [документации по API для `scala.quoted.Quotes.reflectModule`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule.html). 

Наиболее важным элементом на странице является дерево иерархии, 
которое обеспечивает синтетический обзор отношений подтипов типов в API. 
Для каждого типа `Foo` в дереве:

- трейт `FooMethods` содержит методы, доступные для типа `Foo`
- трейт `FooModule` содержит статические методы, доступные для объекта `Foo`. 
В частности, здесь находятся конструкторы (`apply`/`copy`) и `unapply` метод, 
предоставляющий экстракторы, необходимые для сопоставления с образцом.
- Для всех типов `Upper` таких как `Foo <: Upper`, методы, определенные в `UpperMethods`, также доступны для `Foo`

Например, `TypeBounds`, подтип `TypeRepr`, представляет дерево типов в форме `T >: L <: U`: 
тип `T`, который является надтипом `L` и подтипом `U`. 
В `TypeBoundsMethods` есть методы `low` и `hi`, которые позволяют получить доступ к представлениям `L` и `U`. 
В `TypeBoundsModule`, доступен `unapply` метод, который позволяет написать:

```dotty
def f(tpe: TypeRepr) =
  tpe match 
    case TypeBounds(l, u) =>
```

Поскольку `TypeBounds <: TypeRepr`, все методы, определенные в `TypeReprMethods`, доступны для значений `TypeBounds`:

```dotty
def f(tpe: TypeRepr) =
  tpe match
    case tpe: TypeBounds =>
      val low = tpe.low
      val hi  = tpe.hi
```


### Связь с выражением/типом

#### Expr и Term

Выражения (`Expr[T]`) можно рассматривать как обертки вокруг `Term`, где `T` статически известный тип термина. 
Ниже используется метод расширения `asTerm` для преобразования выражения в термин. 
Этот метод расширения доступен только после импорта файлов `quotes.reflect.asTerm`. 
Затем используется `asExprOf[Int]`, чтобы преобразовать термин обратно в `Expr[Int]`. 
Эта операция завершится ошибкой, если термин не имеет указанного типа (в данном случае `Int`) 
или если термин не является допустимым выражением. 
Например, `Ident(fn)` является недопустимым термином, если метод `fn` принимает параметры типа, 
и в этом случае потребуется расширение `Apply(Ident(fn), args)`.

```dotty
def f(x: Expr[Int])(using Quotes): Expr[Int] =
  import quotes.reflect.*
  val tree: Term = x.asTerm
  val expr: Expr[Int] = tree.asExprOf[Int]
  expr
```

#### Type и TypeRepr

Точно так же можно рассматривать `Type[T]` как оболочку над `TypeRepr` со статически известным типом `T`. 
Чтобы получить `TypeRepr`, используется `TypeRepr.of[T]`, 
который ожидает `given Type[T]` в области видимости (аналогично `Type.of[T]`). 
Также можно преобразовать его обратно в `Type[?]` с помощью метода `asType`. 
Поскольку тип `Type[?]` статически неизвестен, нужно вызвать его с реальным типом, чтобы его использовать. 
Этого можно добиться с помощью паттерна `'[t]`.

```dotty
def g[T: Type](using Quotes) =
  import quotes.reflect.*
  val tpe: TypeRepr = TypeRepr.of[T]
  tpe.asType match
    case '[t] => '{ val x: t = ${...} }
  ...
```


### Символы

API-интерфейсы `Term` и `TypeRepr` относительно закрыты в том смысле, 
что методы производят и принимают значения, типы которых определены в API. 
Однако можно заметить наличие `Symbols`, которые идентифицируют определения.

И `Term`, и `TypeRepr` (и, следовательно, `Expr` и `Type`) имеют связанный символ. 
`Symbols` позволяют сравнить два определения по `==`, чтобы узнать, являются ли они одинаковыми. 
Кроме того, `Symbol` раскрывает и использует множество полезных методов. 
Например:

- `declaredFields` и `declaredMethods` позволяет перебирать поля и элементы, определенные внутри символа
- `flags` позволяет проверить несколько свойств символа
- `companionClass` и `companionModule` предоставить способ перехода к сопутствующему объекту/классу и обратно
- `TypeRepr.baseClasses` возвращает список символов родительских классов, расширенных типом
- `Symbol.pos` дает доступ к положению, к исходному коду определения и даже к имени файла, в котором определен символ.
- многие другие, которые можно найти в `SymbolMethods`

#### К символу и обратно

Рассмотрим экземпляр типа `TypeRepr` с именем `val tpe: TypeRepr = ...`. Затем:

- `tpe.typeSymbol` возвращает символ типа, представленного `TypeRepr`. 
Рекомендуемый способ получения `Symbol` given `Type[T]` - `TypeRepr.of[T].typeSymbol`
- Для одноэлементного типа `tpe.termSymbol` возвращает символ базового объекта или значения.
- `tpe.memberType(symbol)` возвращает `TypeRepr` предоставленный символ
- Для объектов `t: Tree` вызов `t.symbol` возвращает символ, связанный с деревом. 
Учитывая, что `Term <: Tree`, `Expr.asTerm.symbol` - это лучший способ получить символ, связанный с `Expr[T]`
- Для объектов `sym: Symbol`, `sym.tree` возвращает `Tree`, связанное с символом. 
Будьте осторожны при использовании этого метода, так как дерево для символа может быть не определено. 
Подробнее читайте на [best practices page](https://docs.scala-lang.org/scala3/guides/macros/best-practices.html)


### API-дизайн макросов

Часто бывает полезно создавать вспомогательные методы или экстракторы, 
которые выполняют некоторую общую логику макросов.

Самыми простыми методами будут те, которые упоминают только `Expr`, `Type` и `Quotes` в своей подписи. 
Внутри они могут использовать отражение, но это не будет видно на месте использования метода.

```dotty
def f(x: Expr[Int])(using Quotes): Expr[Int] =
  import quotes.reflect.*
  ...
```

В некоторых случаях неизбежно, что некоторые методы будут ожидать 
или возвращать `Trees` или другие типы в `quotes.reflect`. 
В этих случаях рекомендуется следовать следующим примерам подписи метода:

Метод, который принимает `quotes.reflect.Term` параметр

```dotty
def f(using Quotes)(term: quotes.reflect.Term): String =
  import quotes.reflect.*
  ...
```

Метод расширения для `quotes.reflect.Term` возврата `quotes.reflect.Tree`

```dotty
extension (using Quotes)(term: quotes.reflect.Term)
  def g: quotes.reflect.Tree = ...
```

Экстрактор, который соответствует `quotes.reflect.Term`

```dotty
object MyExtractor:
  def unapply(using Quotes)(x: quotes.reflect.Term) =
    ...
    Some(y)
```

> Избегайте сохранения контекста `Quotes` в поле. 
> `Quotes` в полях неизбежно усложняют его использование, вызывая ошибки `Quotes`, связанные с разными путями.
> 
> Обычно эти шаблоны встречаются в коде, который использует способы Scala 2 
> для определения методов расширения или контекстных unapply. 
> Теперь, когда есть `given` параметры, которые можно добавлять перед другими параметрами, 
> все эти старые обходные пути больше не нужны. 
> Новые абстракции упрощают как сторону определения, так и сторону использования.


### Отладка

#### Проверки во время выполнения

Выражения (`Expr[T]`) можно рассматривать как обертки вокруг `Term`, где `T` статически известный тип термина. 
Следовательно, эти проверки будут выполняться во время выполнения (т.е. во время компиляции, когда макрос раскрывается).

Рекомендуется включать флаг `-Xcheck-macros` при разработке макроса или при его тестировании. 
Этот флаг активирует дополнительные проверки во время выполнения, 
которые будут пытаться найти неправильно сформированные деревья или типы, как только они будут созданы.

Также есть флаг `-Ycheck:all`, проверяющий все инварианты компилятора на правильность построения дерева. 
Эти проверки обычно заканчиваются ошибкой утверждения.

#### Печать деревьев

Методы `toString` для типов в пакете `quotes.reflect` не подходят для отладки, 
поскольку они показывают внутреннее представление, а не представление `quotes.reflect`. 
Во многих случаях они похожи, но иногда могут сбивать процесс отладки, поэтому на них не следует полагаться.

Вместо этого `quotes.reflect.Printers` предоставляет набор полезных "принтеров" для отладки. 
Примечательно, что классы `TreeStructure`, `TypeReprStructure` и `ConstantStructure` могут быть весьма полезными. 
Они будут печатать древовидную структуру в соответствии с экстракторами, которые потребуются для ее сопоставления.

```dotty
val tree: Tree = ...
println(tree.show(using Printer.TreeStructure))
```

Одно из наиболее полезных мест, где это можно добавить — конец сопоставления с образцом в `Tree`.

```dotty
tree match
  case Ident(_) =>
  case Select(_, _) =>
  ...
  case _ =>
    throw new MatchError(tree.show(using Printer.TreeStructure))
```

Таким образом, если case пропущен, ошибка сообщит о знакомой структуре, 
которую можно скопировать и вставить, чтобы устранить проблемы.

При желании можно сделать этот "принтер" "принтером" по умолчанию:

```dotty
import quotes.reflect.*
given Printer[Tree] = Printer.TreeStructure
...
println(tree.show)
```


---

**Ссылки:**

- [Scala 3 Guide](https://docs.scala-lang.org/scala3/guides/macros/reflection.html)
- [Scala 3 Reference](https://docs.scala-lang.org/scala3/reference/metaprogramming/reflection.html)
